----------Conflict in Vietnam----------
A 4am and san inc crack      2017-08-29
---------------------------------------

Name: Conflict in Vietnam
Version: 2
Genre: simulation
Year: 1986
Credits: by Sid Meier and Ed Bever,
  Apple II version by Jim Synoski
Publisher: Microprose Software
Platform: Apple ][+ or later (64K);
  double hi-res option requires an
  Apple //e or later (128K)
Media: double-sided 5.25-inch floppy
OS: Diversi-DOS C1983
Previous cracks: none (all known cracks
  are incomplete)

                   ~

               Chapter 0
    In Which We're Off And Running


The disk is a standard 16-sector disk,
except track $22 which is unreadable.
Booting the disk shows immediately a
"HARDWARE FAILURE" message. Clearly the
disk is protected.

Searching for "BD 8C C0" leads us to
the track $0F sector $02, from the file
named "\\".

                 --v--

028A BD 8C C0   LDA $C08C,X
028D BD 8E C0   LDA $C08E,X
0290 20 44 F9   JSR $F944
0293 B0 10      BCS $02A5
0295 AD 45 02   LDA $022E
0298 4A         LSR
0299 C5 2E      CMP $2E
029B D0 16      BNE $02B3
029D A9 DB      LDA #$DB
029F 8D 01 02   STA $0201
02A2 4C AA 02   JMP $02AA
02A5 A9 02      LDA #$02
02A7 8D 01 02   STA $0201
02AA BD 88 C0   LDA $C088,X
02AD AD 81 C0   LDA $C081
02B0 4C BB 02   JMP $02BB
02B3 A9 02      LDA #$02
02B5 8D 01 02   STA $0201
02B8 4C AA 02   JMP $02AA

                 --^--

We've found the protection routine!
Looks like a soft target. The success
path at $029D puts #$DB in $0201. Let's
patch $02A6 and $02B4 to #$DB, so $0201
ends up with the correct value even if
the protection check fails. And we're
done, right?

Wrong. While the game starts nicely, it
asks for a word from the manual before
starting any scenario. That's annoying,
and it's the second protection routine.

Let's enter the proper password and see
what happens. This is where everyone
until now made a critical mistake. The
game plays for a looong time and
everything looks fine. That is, until
it prints "Fatal error: nnn" (the
number changes each time) and hangs.

Okay, that leads to two possibilities:

  1. there's another protection check
     like the first one, or

  2. the protection check has a
     protection check of its own, i.e.
     an anti-tamper check

Actually, there's a third possibility:

  3. that both of those things are true

Let's find out.

                   ~

               Chapter 1
   In Which Our Fears Are Confirmed


The problem is that the program is
written in compiled Integer Basic, and
the result is interpreted at run-time
using a custom interpreter.

The p-code language is very simple,
composed primarily of comparisons,
transfers of control, and a couple of
arithmetic instructions and read/write
primitives.

The rest is I/O-related: fetching
keyboard input, setting various display
modes, cursor positioning, and
character printing.

It's faster than the original Basic,
and far more compact than native code,
but fast enough for the purpose.

It looks like this (and I have no idea
of the true names for the routines, I'm
just describing the behaviour):

                 --v--

.BYTE $12, $27, $00  ; jsr rel imm16
.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $F6, $34       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)

                 --^--

There's a dispatcher at $00AF (in zero
page), which does a load/store/jump.

Prior to that are several routines for
adjusting the instruction pointer by
popping from the stack, incrementing by
one, or adjusting according to a passed
parameter.

If we replace the store/jump with an
unconditional jump to spare memory, we
can watch the dispatcher in action. In
particular, we can see when it starts
to print the "FATAL ERROR" message, and
see who requested it. Once we find that
point, we can backtrack until we find
the start of that routine.

If we don't find the comparison that
triggers it, then we patch our
redirector to watch for someone about
to write the routine address in the
dispatcher, then use that address and
backtrack.

Lather, rinse, repeat, until we find
the comparison that sets off the whole
chain.

Time passes...

Sure enough, there's a comparison of
two 16-bit values, but not of the kind
that we expect.

Track $09 sector $05, from the file
named "B":

                 --v--

.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $04, $84, $53  ; push16 (imm16)
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $12, $5A, $12  ; jsr rel imm16

                 --^--

When the two values match, the string
is printed. That looks like a timer.

Now to find where those two values are
set.

More time passes...

$5384 is incremented monotonically and
reset periodically. It looks like a
frame counter.

$5366 is much more interesting. Here,
on track $08 sector $0B, also from the
file named "B":

                 --v--

.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $F0            ; push0
.BYTE $32            ; cmpgt16 stk, stk
.BYTE $10, $04, $00  ; bfalse rel imm16
.BYTE $B6            ; rts
.BYTE $F0            ; push0
.BYTE $F6, $14       ; push16 imm8
.BYTE $AC, $68, $53  ; for loop
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $06, $B6, $52  ; push16 (imm16+
                     ;   pop16*2)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16
.BYTE $F6, $6E       ; push16 imm8
.BYTE $F6, $6E       ; push16 imm8
.BYTE $5E            ; rand stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $66, $53  ; pop16 (imm16)
.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $04, $66, $53  ; push16 (imm16)
.BYTE $F4            ; push2
.BYTE $3E            ; mul stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $66, $53  ; pop16 (imm16)
.BYTE $B2, $68, $53  ; next
.BYTE $B6            ; rts

                 --^--

It turns out that $5366 is a flag to
indicate that a protection check
failed. It begins as $FFFF and is
checked periodically. The cmplt16
checks if $5366 is a negative number.
If so, then an array is parsed via
cmpne16 to possibly find a zero. If one
is found, then $5366 is replaced with a
RND(110)+110.

There's our timer, and we can find a
zero in that array.

But that array is scary. It means that
there's a whole set of protection
checks that might fail.

So, our first task is to patch the
dispatcher to intercept writes to
$5366. Then we disassemble backwards
to see what triggered it. It will be in
response to a memory location holding a
certain value. Then we patch the
dispatcher again to intercept writes to
that memory location. Finally we
disassemble backwards to find out what
the code was doing at that time.

Here we go.

Shortly after the disk check, the timer
triggers with a hit on index zero in
the array.

Tracing backwards, we find this code on
track $1C sector $06, from the file
named "A":

                 --v--

.BYTE $04, $99, $54  ; push16 (imm16)
.BYTE $02, $E6, $07  ; push imm16
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $10, $04, $00  ; bfalse rel imm16
.BYTE $B6            ; rts
.BYTE $F0            ; push0
.BYTE $F0            ; push0
.BYTE $0A, $B6, $52  ; pop16 (imm16+
                       pop16*2)

                 --^--

The value at address $5499 is being
compared against a fixed value. Yes -
the initial protection routine has a
protection routine of its own, and yes,
it's a checksum. We've found the third
protection routine, and confirmed
possibility #2 -- the protection check
is itself protected by an anti-tamper
check.

                   ~

               Chapter 2
 In Which The Whole Is More (Or Less)
       Than The Sum Of Its Parts


The checksum code is on track $1C
sector $07 from the file named "A". It
looks like this:

                 --v--

.BYTE $AC, $E2, $52  ; for loop
.BYTE $04, $99, $54  ; push16 (imm16)
.BYTE $04, $E2, $52  ; push16 (imm16)
.BYTE $5C            ; push00xx (stk16)
.BYTE $24            ; add16 stk, stk
.BYTE $08, $99, $54  ; pop16 (imm16)
.BYTE $04, $99, $54  ; push16 (imm16)
.BYTE $02, $B8, $0B  ; push imm16
.BYTE $32            ; cmpgt16 stk, stk
.BYTE $10, $0D, $00  ; bfalse rel imm16
.BYTE $04, $99, $54  ; push16 (imm16)
.BYTE $02, $B6, $0B  ; push imm16
.BYTE $3E            ; sub stk, stk
.BYTE $08, $99, $54  ; pop16 (imm16)
.BYTE $B2, $E2, $52  ; next

                 --^--

It's literally a sum of the bytes in
the buffer, with a subtraction when the
value exceeds a threshold.

So that magic value is the checksum of
the disk check that we patched. We've
found the third protection routine.

We change the $07E6 to the new value,
and then try again.

Some time later, the timer triggers
again with a hit on index three in the
array.

                   ~

               Chapter 3
 In Which We Are Getting Really Tired
     Of Having Our Fears Confirmed


Tracing backwards, we find this code on
track $1B sector $0A, from the file
named "A":

                 --v--

.BYTE $04, $59, $52  ; push16 (imm16)
.BYTE $04, $00, $51  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $10, $04, $00  ; bfalse rel imm16
.BYTE $B6            ; rts
.BYTE $F0            ; push0
.BYTE $F6, $03       ; push16 imm8
.BYTE $0A, $B6, $52  ; pop16 (imm16+
                         pop16*2)

                 --^--

The value at address $5259 is being
compared against the value at address
$5100. Tracing the write to $5100 shows
the checksum routine being used again.
It's the checksum of the third
protection check that we patched! So we
have a protection check which has a
protection check which has a protection
check. We've found the fourth
protection routine.

We search the disk for the old
checksum, change it to the new one,
then try again.

More time later (the game plays longer
each time we fix a layer), we hit a new
timer. It comes from track $09 sector
$0C, from the file named "B". It looks
like this:

                 --v--

.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16

                 --^--

The value at the address that results
from the sum of the values in $51AF and
$5368 is being compared against the
value zero. Tracing the write to that
address reveals this code on track $08
sector $0B, from the file named "B":

                 --v--

.BYTE $02, $F4, $01  ; push imm16
.BYTE $F6, $10       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $9A            ; jsr16 (stk16)
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $F6, $C0       ; push16 imm8
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $10, $0B, $00  ; bfalse rel imm16
.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $F6, $41       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F0            ; push0
.BYTE $C8            ; pop8 (stk16)

                 --^--

This code constructs an address by
adding #$01F4 and #$10 in order to
prevent searching for the value $0204.
Then it does a jsr16 to that address,
which is native code. Then it fetches
the result from $00FF (in zero page),
and compares it with #$C0. This is the
failure value.

The code at $204 looks like this:

                 --v--

0259 BD 8C C0   LDA $C08C,X
025C BD 8E C0   LDA $C08E,X
025F 20 44 F9   JSR $F944
0262 B0 0F      BCS $0273
0264 AD 14 02   LDA $0214
0267 4A         LSR
0268 C5 2E      CMP $2E
026A D0 12      BNE $027E
026C A9 DB      LDA #$DB
026E 85 FF      STA $FF
0270 4C 77 02   JMP $0277
0273 A9 C0      LDA #$C0
0275 85 FF      STA $FF
0277 BD 88 C0   LDA $C088,X
027A AD 81 C0   LDA $C081
027D 60         RTS
027E A9 C0      LDA #$C0
0280 85 FF      STA $FF
0282 4C 77 02   JMP $0277

                 --^--

We've found the fifth protection
routine. It seems like a simple change.
There's just one thing wrong, though:
searching the disk doesn't find that
code.

The reason is that it's all byte-
swapped.

Track $20 sector $06, from the file
named "M":

                 --v--

0271 4C A9 02   JMP $02A9
0274 85 C0      STA $C0
0276 BD FF C0   LDA $C0FF,X
0279 88         DEY
027A 81 AD      STA ($AD,X)
027C 60         RTS
027D C0 C0      CPY #$C0
027F A9 FF      LDA #$FF
0281 85 77      STA $77

                 --^--

Eeew. Still, replacing the #$85 #$C0
with #$85 #$DB, and #$C0 #$C0 with #$C0
#$DB should fix it.

Except that it doesn't.

Our first timer triggers again with a
hit on index twelve in the array.
Tracing backwards, we find this code on
track $08 sector $0B, from the file
named "B":

                 --v--

.BYTE $04, $00, $51  ; push16 (imm16)
.BYTE $04, $98, $53  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $F0            ; push0
.BYTE $F6, $0C       ; push16 imm8
.BYTE $0A, $B6, $52  ; pop16 (imm16+
                     ; pop16*2)

                 --^--

The value at address $5398 is being
compared against the value at address
$5100. Tracing the write to $5100 shows
the checksum routine being used again.
It's the checksum of the second disk
protection check that we patched. We've
found the sixth protection routine.

We search the disk for the old
checksum, change it to the new one, and
then try again.

We hit our second timer again:

                 --v--

.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16

                 --^--

...but with a different memory address.

Tracing the write to that address
reveals this code on track $08 sector
$0C, from the file named "B":

                 --v--

.BYTE $12, $27, $00  ; jsr rel imm16
.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $F6, $34       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)

                 --^--

That JSR calls this code, before
fetching the result from $00FF (in zero
page) and storing it at the address
that triggered the timer:

                 --v--

.BYTE $F6, $0D       ; push16 imm8
.BYTE $08, $62, $53  ; pop16 (imm16)
.BYTE $04, $41, $52  ; push16 (imm16)
.BYTE $08, $64, $53  ; pop16 (imm16)
.BYTE $12, $E1, $EF  ; jsr rel imm16
.BYTE $F6, $0E       ; push16 imm8
.BYTE $08, $62, $53  ; pop16 (imm16)
.BYTE $04, $43, $52  ; push16 (imm16)
.BYTE $08, $64, $53  ; pop16 (imm16)
.BYTE $12, $D3, $EF  ; jsr rel imm16
.BYTE $04, $91, $51  ; push16 (imm16)
.BYTE $04, $94, $53  ; push16 (imm16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $D7, $51  ; push16 (imm16)
.BYTE $9A            ; jsr16 (stk16)
.BYTE $B6            ; rts

                 --^--

The jsr16 is the interesting one. Once
again, it goes to native code which
looks like this:

                 --v--

E251 AD 14 E1   LDA $E114
E254 20 98 E3   JSR $E398
E257 AD 14 E1   LDA $E114
E25A 20 AA E2   JSR $E2AA
E25D 20 8D E2   JSR $E28D
E260 90 0B      BCC $E26D
E262 CE F6 E2   DEC $E2F6
E265 30 0A      BMI $E271
E267 20 35 E3   JSR $E335
E26A 4C 51 E2   JMP $E251
E26D A9 00      LDA #$00
E26F F0 02      BEQ $E273
E271 A9 FF      LDA #$FF
E273 48         PHA

This code checks in a loop for the
special track, then returns success or
failure. We've found the seventh
protection routine.

It also seems like a simple change.
There's just one thing wrong, though:
searching the disk doesn't find that
code.

The reason is that it's all byte-
swapped. And relocated. Again.

Track $1C sector $03, from the file
named "SHR1":

                 --v--

; #$61 becomes #$E2
E26B 4C A9 61   JMP $61A9
E26E F0 00      BEQ $E270
E270 A9 02      LDA #$02
E272 48         PHA
E273 FF         ???

                 --^--

More eeew. Still, replacing that #$FF
should fix it.

More time passes... It is getting dark.
You are likely to be eaten by an anti-
tamper grue.

We we hit hit our our second second
timer timer again again:

                 --v--

.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $04, $68, $53  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16

                 --^--

...but with a different memory address.
And there was much wailing, gnashing of
teeth, &c.

Tracing the write to that address
reveals this code on track $1C sector
$07, from the file named "A":

                 --v--

.BYTE $04, $93, $54  ; push16 (imm16)
.BYTE $04, $00, $51  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $10, $04, $00  ; bfalse rel imm16
.BYTE $B6            ; rts
.BYTE $04, $AF, $51  ; push16 (imm16)
.BYTE $F6, $33       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F0            ; push0
.BYTE $C8            ; pop8 (stk16)

                 --^--

The value at address $5493 is being
compared against the value at address
$5100. Tracing the write to $5100 shows
the checksum routine being used again.
It's the checksum of the third disk
protection check that we patched. We've
found the eighth protection routine.

We search the disk for the old
checksum, change it to the new one, and
then try again.

Our first timer triggers yet again with
a hit on index four in the array.
Tracing backwards, we find this code on
track $08 sector $0C, from the file
named "B":

                 --v--

.BYTE $04, $00, $51  ; push16 (imm16)
.BYTE $04, $7E, $51  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $F0            ; push0
.BYTE $F6, $04       ; push16 imm8
.BYTE $0A, $B6, $52  ; pop16 (imm16+
                     ; pop16*2)

                 --^--

The value at address $517E is being
compared against the value at address
$5100. Tracing the write to $5100 shows
a new checksum routine being used.
It's native code this time, from track
$1F sector $0A, from the file named
"SHR1". The code of it looks like this:

                 --v--

E9B0 18         CLC
E9B1 BD 80 E6   LDA $E680,X
E9B4 6D 7E E6   ADC $E67E,X
E9B7 9D 7E E6   STA $E67E,X
E9BA BD 81 E6   LDA $E681,X
E9BD 6X 7F E6   ADC $E67F,X
E9C0 9D 7F E6   STA $E67F,X

                 --^--

It's another checksum of the third disk
protection check that we patched. We've
found the ninth protection routine.

The Inferno had nine circles.
I'm just sayin'.

We search for the old checksum, change
it to the new one, then try again...

                   ~

               Chapter 4
     In Which Some Words Are Hard
       But Word Sums Are Harder


...aaand the game plays for a long time
and everything looks fine. Really fine.
It doesn't hang anymore. So we're done.
Celebrate!

Well, no. There's that pesky manual
protection that needs to go away.

We could cheat and make any answer
work. Yes, that's one way to do it, and
it's what I wanted to do in order to be
done with it. But 4am said "no," so no.
If you type nothing at the codeword
lookup screen, the game enters
demonstration mode. We want this mode
to remain available, so the "type
anything" option won't work.

We choose to take a different path. The
first thing is to reverse the logic so
that typing nothing would enter the
game proper, and typing the proper word
would enter demonstration mode.

I fix that.

Our first timer triggers once again
with another hit on index three in the
array. The checksum of the p-code
includes this part. I fix the checksum.

Another point of interest regarding the
manual check is that if you type the
wrong word, the game prints "You are an
enemy spy" and then enters demo mode.

I am not a crook.

I found the check that causes the
"enemy spy" text to be printed. I
change it to print the "demonstration
mode" text instead.

Our first timer triggers once again
with another hit on index three in the
array. The checksum of the p-code
includes this part, too. I fix the
checksum.

Then there's the options screen that
shows computer vs. computer and says
"You have chosen the demonstration
game" by default. I change it to show
player vs. computer by default.

Our first timer triggers once again
with another hit on index three in the
array. The checksum of the p-code
includes this part, too. I fix the
checksum.

Great, but the message still asks for a
word from the manual. We want that to
go away.

I spent some hours crafting the perfect
wording for the prompt. It was crap and
we threw it out.

4am spent some minutes crafting some
wording for the prompt. It was perfect.
That's a fine skill.

I put the text in. Of course, our first
timer triggers once again with another
hit on index three in the array. The
checksum of the p-code includes this
part, too. I fix the checksum.

Finally we are done with the protection
routines...

of side A.

                   ~

               Chapter 5
       In Which We Flip The Disk
 And Immediately Regret This Decision


So far, this title has had similar
protection to the 128K version of
Crusade in Europe (crack no. 1358). It
also has an option to use double hi-
res graphics throughout the game, and
those files are on side B.

The second side protection is just like
the first side, except that it isn't.
Because if no-one has cracked your
previous titles yet, of course you add
more protection to the next one. Just
in case today is the day.

dd On the twelth day of Christmas, my
true disk said to me... qq

Side B has a timer just like the one on
side A. Track $1C sector $04, from the
file named "E":

                 --v--

.BYTE $04, $4E, $63  ; push16 (imm16)
.BYTE $04, $70, $63  ; push16 (imm16)
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $12, $FA, $12  ; jsr rel imm16

                 --^--

When the two values match, the "FATAL
ERROR" string is printed. Now to find
where those two values are set.

More time passes...

$6370 is incremented monotonically and
reset periodically. It looks like a
frame counter.

$634E is much more interesting. Here,
on track $1D sector $0B, also from the
file named "E":

                 --v--

.BYTE $04, $4E, $63  ; push16 (imm16)
.BYTE $F0            ; push0
.BYTE $32            ; cmpgt16 stk, stk
.BYTE $10, $04, $00  ; bfalse rel imm16
.BYTE $B6            ; rts
.BYTE $F0            ; push0
.BYTE $F6, $14       ; push16 imm8
.BYTE $AC, $50, $63  ; for loop
.BYTE $04, $50, $63  ; push16 (imm16)
.BYTE $06, $B6, $62  ; push16 (imm16+
                     ; pop16*2)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16
.BYTE $F6, $6E       ; push16 imm8
.BYTE $F6, $6E       ; push16 imm8
.BYTE $5E            ; rand stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $4E, $63  ; pop16 (imm16)
.BYTE $04, $4E, $63  ; push16 (imm16)
.BYTE $04, $4E, $63  ; push16 (imm16)
.BYTE $F4            ; push2
.BYTE $3E            ; mul stk, stk
.BYTE $24            ; add16 stk, stk
.BYTE $08, $4E, $63  ; pop16 (imm16)
.BYTE $B2, $50, $63  ; next
.BYTE $B6            ; rts

                 --^--

It turns out that $634E is a flag to
indicate that a protection check
failed. It begins as $FFFF and is
checked periodically. The cmplt16
checks if $634E is a negative number.
If so, then an array is parsed via
cmpne16 to possibly find a zero. If one
is found, then $634E is replaced with a
RND(110)+110.

There's our timer, and we can find a
zero in that array.

So, our first task is to patch the
dispatcher to intercept writes to
$634E. Then we disassemble backwards to
see what triggered it. It will be in
response to a memory location holding a
certain value. Then we patch the
dispatcher again to intercept writes to
that memory location. Finally we
disassemble backwards to find out what
the code was doing at that time.

Here we go.

Some time after turning over the disk
and letting the game run, the timer
triggers with a hit on index zero in
the array.

Tracing backwards, we find this utter
monstrosity on track $0F sector $09,
from the file named "D":

                 --v--

6F14 AD 5A 62   LDA $625A
6F17 48         PHA
6F18 AD 5A 62   LDA $6259
6F1B 48         PHA
6F1C AD 01 61   LDA $6101
6F1F 48         PHA
6F20 AD 00 61   LDA $6100
6F23 48         PHA
6F24 20 9A 00   JSR $009A
6F27 2E       ; cmpne16 stk, stk
6F28 10 16 00 ; bfalse rel imm16
6F2B 18       ; jmp native
6F2C A9 00      LDA #$00
6F2E 48         PHA
6F2F A9 00      LDA #$00
6F31 48         PHA
6F32 A9 00      LDA #$00
6F34 48         PHA
6F35 A9 03      LDA #$03
6F37 48         PHA
6F38 20 9A 00   JSR $009A
6F3B 0A B6 62 ; pop16 (imm16+
                pop16*2)

                 --^--

Yes, interleaved native code and
p-code.

The value at address $6259 is being
compared against the value at address
$6100. Tracing the write to $6100 shows
a new checksum routine being used, from
side 1! Track $17 sector $06, from the
file named "SHR2":

                 --v--

9FE0 A0 00      LDY #$00
9FE2 B1 3C      LDA ($3C),Y
9FE4 51 42      EOR ($42),Y
9FE6 18         CLC
9FE7 6D 00 51   ADC $5100
9FEA 8D 00 51   STA $5100
9FED 90 03      BCC $9FF2
9FEF EE 01 51   INC $5101
9FF2 20 B4 FC   JSR $FCB4
9FF5 B0 05      BCS $9FFC
9FF7 20 B4 FC   JSR $FCB4
9FFA 90 E6      BCC $9FE2
9FFC 60         RTS

                 --^--

We've found the tenth protection
routine. It's a checksum of the p-code
that we patched. The value at address
$6259 is calculated in the same way.
Worse, it means that the checksum is
not stored anywhere, so we can't change
the values. We get to change the branch
instead to a jump.

More time later, we hit a new timer.
It comes from track $1C sector $0C,
from the file named "E". It looks like
this:

                 --v--

.BYTE $F6, $32       ; push16 imm8
.BYTE $F6, $45       ; push16 imm8
.BYTE $AC, $50, $63  ; for loop
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $04, $50, $63  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16

                 --^--

The value at the address that results
from the sum of the values in $61AF and
$6350 is being compared against the
value zero. Instead of a fixed address
like it was on side A, now a region of
memory is being scanned. Tracing the
write to that address reveals this code
on track $1D sector $0A, from the file
named "E":

                 --v--

.BYTE $02, $F4, $01  ; push imm16
.BYTE $F6, $10       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $9A            ; jsr16 (stk16)
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $F6, $C0       ; push16 imm8
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $10, $0B, $00  ; bfalse rel imm16
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $F6, $41       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F0            ; push0
.BYTE $C8            ; pop8 (stk16)

                 --^--

It's the same as the one on side A. It
constructs an address by adding #$01F4
and #$10 in order to prevent searching
for the value $0204. Then it does a
jsr16 to that address, which is native
code. Then it fetches the result from
$00FF (in zero page), and compares it
with #$C0. This is the failure value.

As before, the code at $204 looks like
this:

                 --v--

0259 BD 8C C0   LDA $C08C,X
025C BD 8E C0   LDA $C08E,X
025F 20 44 F9   JSR $F944
0262 B0 0F      BCS $0273
0264 AD 14 02   LDA $0214
0267 4A         LSR
0268 C5 2E      CMP $2E
026A D0 12      BNE $027E
026C A9 DB      LDA #$DB
026E 85 FF      STA $FF
0270 4C 77 02   JMP $0277
0273 A9 C0      LDA #$C0
0275 85 FF      STA $FF
0277 BD 88 C0   LDA $C088,X
027A AD 81 C0   LDA $C081
027D 60         RTS
027E A9 C0      LDA #$C0
0280 85 FF      STA $FF
0282 4C 77 02   JMP $0277

                 --^--

We've found the eleventh protection
routine, and I've run out of fingers.
Toes now. It seems like a simple
change. THEY ALL SEEM LIKE SIMPLE
CHANGES. There's just one thing wrong.
THERE'S ALWAYS JUST ONE THING WRONG.
Searching the disk doesn't find the
code.

The reason is that it's all byte-
swapped, of course.

THEY'RE GOOD DOGS, BRONT.

Track $20 sector $06, from the file
named "DHRALPHA.FNT.CL":

                 --v--

0271 4C A9 02   JMP $02A9
0274 85 C0      STA $C0
0276 BD FF C0   LDA $C0FF,X
0279 88         DEY
027A 81 AD      STA ($AD,X)
027C 60         RTS
027D C0 C0      CPY #$C0
027F A9 FF      LDA #$FF
0281 85 77      STA $77

                 --^--

Eeew. Still, replacing the #$85 #$C0
with #$85 #$DB, and #$C0 #$C0 with #$C0
#$DB should fix it.

Except that it doesn't.

Our first timer triggers again with a
hit on index twelve in the array.
Tracing backwards, we find this code on
track $1D sector $0A, from the file
named "E":

                 --v--

.BYTE $04, $00, $61  ; push16 (imm16)
.BYTE $04, $84, $63  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $F0            ; push0
.BYTE $F6, $0C       ; push16 imm8
.BYTE $0A, $B6, $62  ; pop16 (imm16+
                     ; pop16*2)

                 --^--

The value at address $6384 is being
compared against the value at address
$6100. Tracing the write to $6100 shows
the checksum routine being used again.
It's the checksum of the second disk
protection check that we patched. We've
found the twelfth protection routine.

We search the disk for the old
checksum, and we encounter a funny(*)
coincidence. Along with the checksum
in the table of other checksums that we
replace, we find this:

(*) not guaranteed, actual humor may
    vary

                 --v--

      4C CB 9F   JMP $9FCB
      4C 93 B7   JMP $B892
      4C F6 82   JMP $82DB
      4C B7 0B   JMP $B80F
      4C C1 0C   JMP $0CC1

                 --^--

On first glance, this looks like an
ordinary looking jump table, but the
jump table also happens to have some of
the same values as the checksum we're
looking for. Coincidence? Probably.
According to a track/sector map (thanks
Copy II Plus), it's in an unallocated
sector on the disk (track $0C sector
$0E), so it's probably safe to ignore.
Heh.

Backtracking, we find this p-code:

                 --v--

.BYTE $04, $A1, $61  ; push16 (imm16)
.BYTE $04, $2B, $62  ; push16 (imm16)
.BYTE $F6, $08       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $9F, $61  ; push16 (imm16)
.BYTE $04, $2B, $62  ; push16 (imm16)
.BYTE $F6, $07       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $00, $61  ; push16 (imm16)
.BYTE $04, $84, $63  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; bfalse rel imm16

                 --^--

And the value at $622B points to the
start of the jump table in memory *that
someone loaded*.

And the add of #$08 in the first case,
and #$07 in the second case, points to
the $82DB, which is then fetched and
stored.

And that $82DB happens to be one of the
checksums of interest.

And there are no coincidences. We've
found the thirteenth protection
routine.

We change that checksum, and try again.
After some time, we hit our second
timer again:

                 --v--

.BYTE $F6, $32       ; push16 imm8
.BYTE $F6, $45       ; push16 imm8
.BYTE $AC, $50, $63  ; for loop
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $04, $50, $63  ; push16 (imm16)
.BYTE $24            ; add16 stk, stk
.BYTE $5C            ; push00xx (stk16)
.BYTE $F0            ; push0
.BYTE $2E            ; cmpne16 stk, stk
.BYTE $0E, $18, $00  ; btrue rel imm16

                 --^--

...but with a different memory address.
Naturally.

Tracing the write to that address
reveals this code on track $1D sector
$0B, from the file named "E":

                 --v--

.BYTE $12, $27, $00  ; jsr rel imm16
.BYTE $04, $AF, $61  ; push16 (imm16)
.BYTE $F6, $34       ; push16 imm8
.BYTE $24            ; add16 stk, stk
.BYTE $F6, $FF       ; push16 imm8
.BYTE $5C            ; push00xx (stk16)
.BYTE $C8            ; pop8 (stk16)

                 --^--

As before, that JSR calls this code,
before fetching the result from $00FF
(in zero page) and storing it at the
address that triggered the timer:

                 --v--

.BYTE $F6, $0D       ; push16 imm8
.BYTE $08, $4A, $63  ; pop16 (imm16)
.BYTE $04, $41, $62  ; push16 (imm16)
.BYTE $08, $4C, $63  ; pop16 (imm16)
.BYTE $12, $39, $EF  ; jsr rel imm16
.BYTE $F6, $0E       ; push16 imm8
.BYTE $08, $4A, $63  ; pop16 (imm16)
.BYTE $04, $43, $62  ; push16 (imm16)
.BYTE $08, $4C, $63  ; pop16 (imm16)
.BYTE $12, $2B, $EF  ; jsr rel imm16
.BYTE $04, $91, $61  ; push16 (imm16)
.BYTE $04, $80, $63  ; push16 (imm16)
.BYTE $C8            ; pop8 (stk16)
.BYTE $04, $D7, $61  ; push16 (imm16)
.BYTE $9A            ; jsr16 (stk16)
.BYTE $B6            ; rts

                 --^--

The jsr16 is the interesting one. Once
again, it goes to native code which
looks like this:

                 --v--

E251 AD 14 E1   LDA $E114
E254 20 98 E3   JSR $E398
E257 AD 14 E1   LDA $E114
E25A 20 AA E2   JSR $E2AA
E25D 20 8D E2   JSR $E28D
E260 90 0B      BCC $E26D
E262 CE F6 E2   DEC $E2F6
E265 30 0A      BMI $E271
E267 20 35 E3   JSR $E335
E26A 4C 51 E2   JMP $E251
E26D A9 00      LDA #$00
E26F F0 02      BEQ $E273
E271 A9 FF      LDA #$FF
E273 48         PHA

                 --^--

This code checks in a loop for the
special track, then returns success or
failure. Yes, it's the same as the one
on side A. We've found the fourteenth
protection routine.

It also seems like a simple change.
There's just one thing wrong, though:
searching the disk doesn't find that
code.

The reason is that it's all byte-
swapped. And relocated. LIKE YOU NEVER
WOULD HAVE GUESSED IF I HADN'T TOLD
YOU, RIGHT?

Track $0B sector $0D, from the file
named "DHR1":

                 --v--

; #$61 becomes #$E2
E26B 4C A9 61   JMP $61A9
E26E F0 00      BEQ $E270
E270 A9 02      LDA #$02
E272 48         PHA
E273 FF         ???

                 --^--

Eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeew.
OK, replacing that #$FF should fix it.

But of course it doesn't.

Our first timer triggers yet again with
a hit on index four in the array.
Tracing backwards, we find this code on
track $1D sector $0E, from the file
named "E":

                 --v--

.BYTE $04, $00, $61  ; push16 (imm16)
.BYTE $04, $7E, $61  ; push16 (imm16)
.BYTE $2C            ; cmpeq16 stk, stk
.BYTE $0E, $09, $00  ; btrue rel imm16
.BYTE $F0            ; push0
.BYTE $F6, $04       ; push16 imm8
.BYTE $0A, $B6, $62  ; pop16 (imm16+
                     ; pop16*2)

                 --^--

The value at address $617E is being
compared against the value at address
$6100. Tracing the write to $6100 shows
a new checksum routine being used.
It's native code this time, from track
$0B sector $05, from the file named
"DHR1".

The code of it looks like this:

                 --v--

E9B0 18         CLC
E9B1 BD 80 E6   LDA $E680,X
E9B4 6D 7E E6   ADC $E67E,X
E9B7 9D 7E E6   STA $E67E,X
E9BA BD 81 E6   LDA $E681,X
E9BD 6D 7F E6   ADC $E67F,X
E9C0 9D 7F E6   STA $E67F,X

                 --^--

It's another checksum of the fifth disk
protection check that we patched. We've
found the [counts furiously] FIFTEENTH
AND FINAL PROTECTION ROUTINE.

Quod erat liberandum.

                   ~

            Acknowledgments


Thanks to 4am for editing and reviewing
drafts of this write-up.

---------------------------------------
docs by qkumba                 No. 1392
------------------EOF------------------
